Controlling State
With all of the possible parameters of the Draw overloads discussed, now it is time to look at the multiple overloads of the Begin
method. Although we use this method in every example up until this
point, you used only the overload with no parameters, and that doesn’t
require much explanation. Each example up until now has had only a
single Draw call, but as the name
sprite batch implies, you can draw many images at once, and you need
some way to control how each image interacts with every other. This is
what the Begin overloads do.
Before we get into that,
however, add an existing item to your content project and include the
image layers.jpg from the downloadable examples. This image has a few
pictures and numbers on it to help better demonstrate the behavior of
multiple Draw calls in a single batch. Update your load content method as always when you add new content to your project:
texture = Content.Load<Texture2D>("Layers");
Again, replace the contents of your Draw method with the following:
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin(SpriteSortMode.Texture, null);
for (int i = 0; i < 4; i++)
{
Rectangle src = new Rectangle((i % 2) * (texture.Width / 2),
(i < 2) ? 0: (texture.Height / 2) ,
texture.Width/2, texture.Height/2);
spriteBatch.Draw(texture, new Vector2(50 + (50*i), 50 + (50*i)), src,
Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, i * 0.1f);
}
spriteBatch.End();
base.Draw(gameTime);
This uses the largest overload of the Draw
method and renders four different squares of the texture each at a
different spot on the screen. Notice also the layer depth is passed so
that each image is rendered at a different depth with the image
containing the number 1 drawn at a depth of 0.1f and with the image
containing the number 4 drawn at a depth of 0.4f. Also notice that this
code renders the images in the order of 1,2,3,4. However, when you run
the code, it is not drawn like that—it draws seemingly random, much
like in Figure 3.
However, if you change the call to Begin with the following, it draws the images with the first image on the bottom and the last image on top, much like you see in Figure 4.
spriteBatch.Begin(SpriteSortMode.FrontToBack, null);
This is because this overload of the Begin call includes the first parameter of type SpriteSortMode,
which controls how multiple sprites sort within this batch before drawn
on the screen. The options for this enumeration include FrontToBack
as seen here, which renders images with the highest layer depth “on
top” and the lowest layer depth “on bottom.” The image with the 4 on it
is rendered on top because it has the highest layer depth. If you
instead switched this to BackToFront,
the order reverses itself, and the lowest layer depth is “on top,” and
in this case, the image with the 1 is rendered on the top.
Another option for sorting is the default sorting option Texture. This causes the Draw calls to sort by the texture. In the previous example code, all of the Draw
calls use the same texture, so there is no special sorting. This is the
default because drawing a few images, switching textures, drawing a few
more images, switching back to the original texture, and drawing a few
more images can hinder performance.
Other options for the sorting mode include Deferred. Each image sorts in the order it is used in the Draw calls. This means that all Draw
calls should be “batched up” to make as few actual rendering calls to
the hardware as possible. This is because an actual rendering call on
the hardware can be expensive, and it is better to make fewer rendering
calls that render lots of data instead of a large amount of render
calls that render small amounts of data. All options in the sorting
mode except for one also infers the behavior of Deferred.
The last sort mode is the one that doesn’t follow the Deferred behavior and is called Immediate. This tells the sprite batch to make a call to the rendering hardware for every call to Draw you make.
The other parameter to Begin shown previously and that is currently null is the BlendState
you want to use for this sprite batch. Although we discuss the blend
state (and the other states) in later chapters, now is a good time to
understand the basics of blending. As the name implies, blending
controls how multiple images are combined. The default value (what is
used if you pass in null) is BlendState.AlphaBlend.
The alpha values control the transparency of objects drawn, so this
blending state tells the sprite batch that for every pixel it draws, it
should render the top-most pixel if the pixels are opaque (have no
alpha value), or it should blend the top most pixel with the pixels
“underneath” it.
In your content project,
add another existing item, AlphaSprite.png. This item is a png (much
like you did when you picked the animated sprite), because this file
format can include alpha data. Instead of using the same texture object
you’ve been using, though, add a new texture variable to your class so
you can see how things blend together:
Of course, you need to load this texture, so add that to the LoadContent method:
alphaTexture = Content.Load<Texture2D>("AlphaSprite");
Then replace the Draw method with the following:
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
spriteBatch.Draw(texture, Vector2.Zero, Color.White);
spriteBatch.Draw(alphaTexture, Vector2.Zero, Color.White);
spriteBatch.End();
base.Draw(gameTime);
This renders the full four-picture sprite you used previously along with a little character in the middle of it (see Figure 5).
Now change the blend state from alpha blending to BlendState.Opaque, which doesn’t attempt to blend the two images. Because of the Deferred
sort mode, it renders them in the order they appear, so it renders the
character last. Notice that you can’t see anything other than the
character, and the portions that used to be transparent are now black.
Change it again to BlendState.Additive and notice a weird combination of the two images that look too “bright.” This is caused by “adding” the colors together.